﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntModApp.EntServices.Data;
using System.Data.Entity;
using System.Diagnostics.Contracts;
using System.Linq.Expressions;
using System.Data.Entity.Infrastructure;
using System.Data;

namespace EntModApp.EntServices.Data
{

  public class DbContextQueryableRepository<TContext, TEntity>
    : IQueryableRepository<TEntity>
    where TContext : DbContext
    where TEntity : class
  {
    protected DbContextQueryableRepository(TContext context, DbSet<TEntity> dataSet, bool ownContext)
    {
      Contract.Requires(context != null, "context != null");
      Contract.Requires(dataSet != null, "dataSet != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");
      Contract.Ensures(OwnContext == ownContext, "OwnContext == ownContext");
      Contract.Ensures(Context == context, "Context == context");
      Contract.Ensures(DataSet == dataSet, "DataSet == dataSet");

      OwnContext = ownContext;
      Context = context;
      DataSet = dataSet;
    }

    public bool OwnContext { get; private set; }

    public TContext Context { get; private set; }

    public DbSet<TEntity> DataSet { get; private set; }

    public virtual IQueryable<TEntity> All()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return IncludePaths(DataSet).AsQueryable<TEntity>();
    }

    public virtual IQueryable<TEntity> All(out int total, int index = 0, int size = 10)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(index >= 0, "index >= 0");
      Contract.Requires(size > 0, "size > 0");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(Contract.ValueAtReturn<int>(out total) >= 0, "total >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      int skipCount = size * index;
      IQueryable<TEntity> query = IncludePaths(DataSet);

      total = query.Count();

      if (skipCount > 0)
        query = query.Skip(skipCount);
      query = query.Take(size);
      return query;
    }

    public virtual IQueryable<TEntity> Filter(Expression<Func<TEntity, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return IncludePaths(DataSet).Where(predicate);
    }

    public virtual IQueryable<TEntity> Filter(out int total, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> sortingSelector = null, Expression<Func<TEntity, bool>> filter = null, int index = 0, int size = 10)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(index >= 0, "index >= 0");
      Contract.Requires(size > 0, "size > 0");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(Contract.ValueAtReturn<int>(out total) >= 0, "total >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      int skipCount = index * size;
      IQueryable<TEntity> query = IncludePaths(DataSet);
      if (filter != null)
        query = query.Where(filter);

      total = query.Count();

      if (sortingSelector != null)
        query = sortingSelector(query);
      if (skipCount > 0)
        query = query.Skip(skipCount);
      query = query.Take(size);
      return query;
    }

    public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return IncludePaths(DataSet).FirstOrDefault(predicate);
    }

    public virtual bool Contains(Expression<Func<TEntity, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return DataSet.Where(predicate).Take(1).Count() > 1;
    }

    public virtual int Count()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(Contract.Result<int>() >= 0, "result >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return DataSet.Count();
    }

    public virtual int Count(Expression<Func<TEntity, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(Contract.Result<int>() >= 0, "result >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return DataSet.Count(predicate);
    }

    public virtual TEntity Create(TEntity t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var result = DataSet.Add(t);
      SaveIfOwned();
      return result;
    }

    public virtual void Delete(TEntity t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.Entry<TEntity>(t);
      DataSet.Remove(t);
      SaveIfOwned();
    }

    public virtual int Delete(Expression<Func<TEntity, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");
      Contract.Ensures(Contract.Result<int>() >= 0, "result >= 0");

      IQueryable<TEntity> query = DataSet.Where(predicate);
      foreach (var item in query)
      {
        DataSet.Remove(item);
      }
      SaveIfOwned();
      return query.Count();
    }

    public virtual TEntity Update(TEntity t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var entry = Context.Entry<TEntity>(t);
      DataSet.Attach(t);
      entry.State = EntityState.Modified;
      SaveIfOwned();
      return t;
    }

    public virtual void Commit()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.SaveChanges();
    }

    public void Dispose()
    {
      if (IsDisposed) return;
      IsDisposed = true;
      if (this.OwnContext)
        this.Context.Dispose();
      GC.SuppressFinalize(this);
    }

    public bool IsDisposed { get; private set; }

    protected virtual IQueryable<TEntity> IncludePaths(IQueryable<TEntity> query)
    {
      return query;
    }

    protected int SaveIfOwned()
    {
      Contract.Assert(!IsDisposed, "!IsDisposed");

      if (OwnContext)
        return Context.SaveChanges();
      return 0;
    }
  }

  public class DbContextQueryableRepository<TContext, TEntity, TDerived>
    : IQueryableRepository<TDerived>
    where TContext : DbContext
    where TEntity : class
    where TDerived : class, TEntity
  {
    protected DbContextQueryableRepository(TContext context, DbSet<TEntity> dataSet, bool ownContext)
    {
      Contract.Requires(context != null, "context != null");
      Contract.Requires(dataSet != null, "dataSet != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");
      Contract.Ensures(OwnContext == ownContext, "OwnContext == ownContext");
      Contract.Ensures(Context == context, "Context == context");
      Contract.Ensures(DataSet == dataSet, "DataSet == dataSet");

      OwnContext = ownContext;
      Context = context;
      DataSet = dataSet;
    }

    public bool OwnContext { get; private set; }

    public TContext Context { get; private set; }

    public DbSet<TEntity> DataSet { get; private set; }

    public virtual IQueryable<TDerived> All()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return IncludePaths(DataSet.OfType<TDerived>());
    }

    public virtual IQueryable<TDerived> All(out int total, int index = 0, int size = 10)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(index >= 0, "index >= 0");
      Contract.Requires(size > 0, "size > 0");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(Contract.ValueAtReturn<int>(out total) >= 0, "total >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      int skipCount = size * index;
      IQueryable<TDerived> query = IncludePaths(DataSet.OfType<TDerived>());

      total = query.Count();

      if (skipCount > 0)
        query = query.Skip(skipCount);
      query = query.Take(size);
      return query;
    }

    public virtual IQueryable<TDerived> Filter(Expression<Func<TDerived, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return IncludePaths(DataSet.OfType<TDerived>()).Where(predicate);
    }

    public virtual IQueryable<TDerived> Filter(out int total, Func<IQueryable<TDerived>, IOrderedQueryable<TDerived>> sortingSelector = null, Expression<Func<TDerived, bool>> filter = null, int index = 0, int size = 10)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(index >= 0, "index >= 0");
      Contract.Requires(size > 0, "size > 0");

      Contract.Ensures(Contract.Result<IQueryable<TEntity>>() != null, "result != null");
      Contract.Ensures(Contract.ValueAtReturn<int>(out total) >= 0, "total >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      int skipCount = index * size;
      IQueryable<TDerived> query = IncludePaths(DataSet.OfType<TDerived>());
      if (filter != null)
        query = query.Where(filter);

      total = query.Count();

      if (sortingSelector != null)
        query = sortingSelector(query);
      if (skipCount > 0)
        query = query.Skip(skipCount);
      query = query.Take(size);
      return query;
    }

    public virtual TDerived Find(Expression<Func<TDerived, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return IncludePaths(DataSet.OfType<TDerived>()).FirstOrDefault(predicate);
    }

    public virtual bool Contains(Expression<Func<TDerived, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return DataSet.OfType<TDerived>().Where(predicate).Take(1).Count() > 1;
    }

    public virtual int Count()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(Contract.Result<int>() >= 0, "result >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return DataSet.OfType<TDerived>().Count();
    }

    public virtual int Count(Expression<Func<TDerived, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(Contract.Result<int>() >= 0, "result >= 0");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      return DataSet.OfType<TDerived>().Count(predicate);
    }

    public virtual TDerived Create(TDerived t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(Contract.Result<TEntity>() != null, "result != null");
      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var result = (TDerived)DataSet.Add(t);
      SaveIfOwned();
      return result;
    }

    public virtual void Delete(TDerived t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(t != null, "t != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.Entry<TEntity>(t);
      DataSet.Remove(t);
      SaveIfOwned();
    }

    public virtual int Delete(Expression<Func<TDerived, bool>> predicate)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");
      Contract.Requires(predicate != null, "predicate != null");

      Contract.Ensures(!IsDisposed, "!IsDisposed");
      Contract.Ensures(Contract.Result<int>() >= 0, "result >= 0");

      IQueryable<TDerived> query = DataSet.OfType<TDerived>().Where(predicate);
      foreach (var item in query)
      {
        DataSet.Remove(item);
      }
      SaveIfOwned();
      return query.Count();
    }

    public virtual TDerived Update(TDerived t)
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      var entry = Context.Entry<TEntity>(t);
      DataSet.Attach(t);
      entry.State = EntityState.Modified;
      SaveIfOwned();
      return t;
    }

    public virtual void Commit()
    {
      Contract.Requires(!IsDisposed, "!IsDisposed");

      Contract.Ensures(!IsDisposed, "!IsDisposed");

      Context.SaveChanges();
    }

    public void Dispose()
    {
      if (IsDisposed) return;
      IsDisposed = true;
      if (this.OwnContext)
        this.Context.Dispose();
      GC.SuppressFinalize(this);
    }

    public bool IsDisposed { get; private set; }

    protected virtual IQueryable<TDerived> IncludePaths(IQueryable<TDerived> query)
    {
      return query;
    }

    protected int SaveIfOwned()
    {
      Contract.Assert(!IsDisposed, "!IsDisposed");

      if (OwnContext)
        return Context.SaveChanges();
      return 0;
    }
  }
}
